package ch.zhaw.cctd.logic.network;

import java.net.MalformedURLException;
import java.rmi.AccessException;
import java.rmi.ConnectException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Übernimmt die Verantwortung für die RMI-Registry und die Anmeldung des Clients beim Server.
 * @author Benjamin Felder
 */
public class Networking {
	
	private static final Logger logger = LoggerFactory.getLogger(Networking.class);
	
	public static final int DEFAULT_PORT = 2020;
	public static final String REMOTE_CLIENT_BIND = "cctdRemoteClient";
	
	/*
	 * tries to connect to the rmi-registry and if none is around simply creates one locally.
	 */
	private void initRmiRegistry(int port) throws NetworkException {
		try {
			Registry registry = LocateRegistry.getRegistry(port);
			registry.list();
			logger.trace("using old RMI Registry at Port {}", port);
			// This call will throw a ConnectException
			// if the registry does not exist
		} catch (ConnectException ce) {
			// No valid registry at that port.
			logger.trace("Creating new RMI Registry at Port {}", port);
			try {
				LocateRegistry.createRegistry(port);
			} catch (RemoteException e) {
				throw new NetworkException("failed to create local rmi registry", e);
			}
		} catch (RemoteException e) {
			logger.error("an error occured");
			throw new NetworkException(e);
		}
	}
	
	/**
	 * Connects to the rmi-registry at the specified address with the default port.
	 * @see #joinGame(String, int)
	 */
	public RemoteServer joinGame(String address) throws NetworkException {
		return this.joinGame(address, DEFAULT_PORT);
	}
	
	/**
	 * Connects to the rmi-registry at the specified address and port.
	 * @param address	the address of the cctd-server
	 * @param port		the port of the cctd-server
	 * @return		the <code>RemoteServer</code> of the server.
	 * @throws NetworkException	if an error occured during joining.
	 */
	public RemoteServer joinGame(String address, int port) throws NetworkException {
		if(0<port && port <=65536) {
			address = address + ":" + port;
		}
		final String URL = "rmi://"+address+"/"+ Networking.REMOTE_CLIENT_BIND;
		try {
			logger.trace("loading {}", URL);
			RemoteServer server = (RemoteServer) Naming.lookup(URL);
			logger.trace("Connected to {}", address);
			return server;
		} catch (ConnectException e) {
			throw new NetworkException("Connection to "+address+" refused", e);
		} catch (NotBoundException e) {
			throw new NetworkException("No CCTD running on specified host", e);
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException("no valid addresse and/or port specified", e);
		} catch (RemoteException e) {
			throw new NetworkException("unknown remote error", e, true);
		}
	}
	
	/**
	 * Starts a cctd-server at the default port.
	 * @see #hostGame(RemoteServer, int)
	 */
	public void hostGame(RemoteServer match) throws NetworkException {
		this.hostGame(match, DEFAULT_PORT);
	}
	
	/**
	 * Starts a cctd-server at the specified port.
	 * @param matchServer	the server class to bind.
	 * @param port		the port to provide the game
	 * @throws NetworkException		if an error occured during joining
	 */
	public void hostGame(RemoteServer matchServer, int port) throws NetworkException {
		final String URL = "rmi://localhost:"+port+"/"+ Networking.REMOTE_CLIENT_BIND;
		try {
			this.initRmiRegistry(port);
			logger.trace("Bind MatchServer to rmi {}", URL);
			Naming.rebind(URL, matchServer);
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException("no valid addresse and/or port specified", e);
		} catch (AccessException e) {
			throw new NetworkException("access to local rmi registry denied", e, true);
		} catch (ServerException e) {
			if (e.getCause() instanceof AccessException) {
				throw new NetworkException("access to remote rmi registry denied", e, true);
			}
			throw new NetworkException("unknown remote error", e, true);
		} catch (RemoteException e) {
			throw new NetworkException("unknown remote error", e, true);
		}
	}
	
	public void cleanup() {
		logger.trace("cleanup rmi");
	}
	
}
